/* Soot - a J*va Optimization Framework
 * Copyright (C) 2004 Jennifer Lhotak
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

package soot.javaToJimple;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import polyglot.ast.Block;
import polyglot.ast.FieldDecl;
import soot.MethodSource;
import soot.PackManager;
import soot.Scene;
import soot.SootClass;
import soot.SootField;

public class PolyglotMethodSource implements MethodSource {

    private polyglot.ast.Block block;
    private List formals;
    private ArrayList<FieldDecl> fieldInits;
    private ArrayList<FieldDecl> staticFieldInits;
    private ArrayList<Block> initializerBlocks;
    private ArrayList<Block> staticInitializerBlocks;
    private soot.Local outerClassThisInit;
    private boolean hasAssert = false;
    private ArrayList<SootField> finalsList;
    private HashMap newToOuterMap;
    private AbstractJimpleBodyBuilder ajbb;
    
    public PolyglotMethodSource(){
        this.block = null;
        this.formals = null;
    }
    
    public PolyglotMethodSource(polyglot.ast.Block block, List formals){
        this.block = block;
        this.formals = formals;
    }

    public soot.Body getBody(soot.SootMethod sm, String phaseName) {
        //JimpleBodyBuilder jbb = new JimpleBodyBuilder();
        soot.jimple.JimpleBody jb = ajbb.createJimpleBody(block, formals, sm);
       
        PackManager.v().getPack("jj").apply(jb);
        return jb;
    }

    public void setJBB(AbstractJimpleBodyBuilder ajbb){
        this.ajbb = ajbb;
    }

    public void setFieldInits(ArrayList<FieldDecl> fieldInits){
        this.fieldInits = fieldInits;
    }
    
    public void setStaticFieldInits(ArrayList<FieldDecl> staticFieldInits){
        this.staticFieldInits = staticFieldInits;
    }

    public ArrayList<FieldDecl> getFieldInits() {
        return fieldInits;
    }
    
    public ArrayList<FieldDecl> getStaticFieldInits() {
        return staticFieldInits;
    }

    public void setStaticInitializerBlocks(ArrayList<Block> staticInits) {
        staticInitializerBlocks = staticInits;
    }
    
    public void setInitializerBlocks(ArrayList<Block> inits) {
        initializerBlocks = inits;
    }

    public ArrayList<Block> getStaticInitializerBlocks() {
        return staticInitializerBlocks;
    }
    
    public ArrayList<Block> getInitializerBlocks() {
        return initializerBlocks;
    }
    
    public void setOuterClassThisInit(soot.Local l) {
        outerClassThisInit = l;
    }

    public soot.Local getOuterClassThisInit(){
        return outerClassThisInit;
    }

    public boolean hasAssert(){
        return hasAssert;
    }

    public void hasAssert(boolean val){
        hasAssert = val;
    }

    public void addAssertInits(soot.Body body){
        // if class is inner get desired assertion status from outer most class
        soot.SootClass assertStatusClass = body.getMethod().getDeclaringClass();
        HashMap<SootClass, InnerClassInfo> innerMap = soot.javaToJimple.InitialResolver.v().getInnerClassInfoMap();
        while ((innerMap != null) && (innerMap.containsKey(assertStatusClass))){
            assertStatusClass = innerMap.get(assertStatusClass).getOuterClass();
        }

        String paramName = assertStatusClass.getName();
        String fieldName = "class$"+assertStatusClass.getName().replaceAll(".", "$");
        
        if (assertStatusClass.isInterface()){
            assertStatusClass = InitialResolver.v().specialAnonMap().get(assertStatusClass);
        }
        
        // field ref
        soot.SootFieldRef field = soot.Scene.v().makeFieldRef(assertStatusClass, fieldName, soot.RefType.v("java.lang.Class"), true);

        soot.Local fieldLocal = soot.jimple.Jimple.v().newLocal("$r0", soot.RefType.v("java.lang.Class"));

        body.getLocals().add(fieldLocal);
        
        soot.jimple.FieldRef fieldRef = soot.jimple.Jimple.v().newStaticFieldRef(field);
        
        soot.jimple.AssignStmt fieldAssignStmt = soot.jimple.Jimple.v().newAssignStmt(fieldLocal, fieldRef);

        body.getUnits().add(fieldAssignStmt);

        // if field not null
        soot.jimple.ConditionExpr cond = soot.jimple.Jimple.v().newNeExpr(fieldLocal, soot.jimple.NullConstant.v());
        
        soot.jimple.NopStmt nop1 = soot.jimple.Jimple.v().newNopStmt();

        soot.jimple.IfStmt ifStmt = soot.jimple.Jimple.v().newIfStmt(cond, nop1);
        body.getUnits().add(ifStmt);

        // if alternative
        soot.Local invokeLocal = soot.jimple.Jimple.v().newLocal("$r1", soot.RefType.v("java.lang.Class"));

        body.getLocals().add(invokeLocal);
        
        ArrayList paramTypes = new ArrayList();
        paramTypes.add(soot.RefType.v("java.lang.String"));
                
        soot.SootMethodRef methodToInvoke = soot.Scene.v().makeMethodRef(assertStatusClass, "class$", paramTypes, soot.RefType.v("java.lang.Class"), true);

        ArrayList params = new ArrayList();
        params.add(soot.jimple.StringConstant.v(paramName));
        soot.jimple.StaticInvokeExpr invoke = soot.jimple.Jimple.v().newStaticInvokeExpr(methodToInvoke, params);
        soot.jimple.AssignStmt invokeAssign = soot.jimple.Jimple.v().newAssignStmt(invokeLocal, invoke);
        
        body.getUnits().add(invokeAssign);

        // field ref assign
        soot.jimple.AssignStmt fieldRefAssign = soot.jimple.Jimple.v().newAssignStmt(fieldRef, invokeLocal);

        body.getUnits().add(fieldRefAssign);

        soot.jimple.NopStmt nop2 = soot.jimple.Jimple.v().newNopStmt();

        soot.jimple.GotoStmt goto1 = soot.jimple.Jimple.v().newGotoStmt(nop2);

        body.getUnits().add(goto1);
        // add nop1 - and if consequence
        body.getUnits().add(nop1);

        soot.jimple.AssignStmt fieldRefAssign2 = soot.jimple.Jimple.v().newAssignStmt(invokeLocal, fieldRef);

        body.getUnits().add(fieldRefAssign2);

        body.getUnits().add(nop2);

        // boolean tests
        soot.Local boolLocal1 = soot.jimple.Jimple.v().newLocal("$z0", soot.BooleanType.v());
        body.getLocals().add(boolLocal1);
        soot.Local boolLocal2 = soot.jimple.Jimple.v().newLocal("$z1", soot.BooleanType.v());
        body.getLocals().add(boolLocal2);

        // virtual invoke
        soot.SootMethodRef vMethodToInvoke = Scene.v().makeMethodRef(soot.Scene.v().getSootClass("java.lang.Class"), "desiredAssertionStatus", new ArrayList(), soot.BooleanType.v(), false);
        soot.jimple.VirtualInvokeExpr vInvoke = soot.jimple.Jimple.v().newVirtualInvokeExpr(invokeLocal, vMethodToInvoke, new ArrayList());

        
        soot.jimple.AssignStmt testAssign = soot.jimple.Jimple.v().newAssignStmt(boolLocal1, vInvoke);

        body.getUnits().add(testAssign);
        
        // if
        soot.jimple.ConditionExpr cond2 = soot.jimple.Jimple.v().newNeExpr(boolLocal1, soot.jimple.IntConstant.v(0));

        soot.jimple.NopStmt nop3 = soot.jimple.Jimple.v().newNopStmt();
        
        soot.jimple.IfStmt ifStmt2 = soot.jimple.Jimple.v().newIfStmt(cond2, nop3);
        body.getUnits().add(ifStmt2);

        // alternative
        soot.jimple.AssignStmt altAssign = soot.jimple.Jimple.v().newAssignStmt(boolLocal2, soot.jimple.IntConstant.v(1));

        body.getUnits().add(altAssign);

        soot.jimple.NopStmt nop4 = soot.jimple.Jimple.v().newNopStmt();

        soot.jimple.GotoStmt goto2 = soot.jimple.Jimple.v().newGotoStmt(nop4);

        body.getUnits().add(goto2);

        body.getUnits().add(nop3);
        
        soot.jimple.AssignStmt conAssign = soot.jimple.Jimple.v().newAssignStmt(boolLocal2, soot.jimple.IntConstant.v(0));

        body.getUnits().add(conAssign);

        body.getUnits().add(nop4);
        
        // field assign
        soot.SootFieldRef fieldD = Scene.v().makeFieldRef(body.getMethod().getDeclaringClass(), "$assertionsDisabled", soot.BooleanType.v(), true);

        soot.jimple.FieldRef fieldRefD = soot.jimple.Jimple.v().newStaticFieldRef(fieldD);
        soot.jimple.AssignStmt fAssign = soot.jimple.Jimple.v().newAssignStmt(fieldRefD, boolLocal2);
        body.getUnits().add(fAssign);
        
    }

    public void setFinalsList(ArrayList<SootField> list){
        finalsList = list;
    }

    public ArrayList<SootField> getFinalsList(){
        return finalsList;
    }

    public void setNewToOuterMap(HashMap map){
        newToOuterMap = map;
    }

    public HashMap getNewToOuterMap(){
        return newToOuterMap;
    }
}
